#include <linux/kernel.h>
#include <linux/sysfs.h>
#include <linux/slab.h>
#include <linux/stat.h>

#include "buzzer.h"

static void buzzer_release_dev(struct device *dev)
{
	struct buzzer_device *buzzer_device = container_of(dev, struct buzzer_device, device);

	gpio_pwm_release(buzzer_device->buzzer_pin);

	device_remove_file(dev->parent,&buzzer_device->attr);
	kfree(dev->init_name);
	kfree(dev);
}

static ssize_t buzzer_show(struct device *dev, struct device_attribute *attr, char *buf);
static ssize_t buzzer_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);

struct buzzer_device* buzzer_init(unsigned int gpio_pin, char *name, struct device *parent)
{
	struct buzzer_device *dev = kmalloc(sizeof(struct buzzer_device), GFP_KERNEL);
	size_t len = strlen(name) + 1;

	if(!dev)
		return (struct buzzer_device*)-ENOMEM;

	memset(dev, 0, sizeof(struct buzzer_device));             // init data to zero

	dev->device.init_name = kmalloc(len, GFP_KERNEL);         // init data
	memset(dev->device.init_name, 0, len);
	strncpy(dev->device.init_name, name, len);
	dev->device.parent = parent;
	dev->device.release = &buzzer_release_dev;

	dev->attr.attr.name = "value";
	dev->attr.attr.mode = S_IRUGO | S_IWUGO;                  // read/write
	dev->attr.show = &buzzer_show;                            // read handler
	dev->attr.store = &buzzer_store;                          // write handler

	dev->buzzer_pin = gpio_pwm_init(gpio_pin);                // allocate gpio_pin to be used by pwm as in pwm.c
	gpio_pwm_set_polarity(dev->buzzer_pin, 1);

	if((int)dev->buzzer_pin == -ENODEV)
		return (struct buzzer_device*)-ENODEV;

	int ret = device_register(&dev->device);
	if(ret) return (struct buzzer_device*)-ENODEV;

	ret = device_create_file(&dev->device,&dev->attr);
	if(ret)
		return (struct buzzer_device*)ret;

	return dev;
}

void buzzer_release(struct buzzer_device *dev)
{
	device_unregister(&dev->device);
}

static ssize_t buzzer_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	struct buzzer_device *buzzer_device = container_of(dev, struct buzzer_device, device);

	sprintf(buf, "%d\n", buzzer_device->freq);
	return strlen(buf);
}

static ssize_t buzzer_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	struct buzzer_device *buzzer_device = container_of(dev, struct buzzer_device, device);

	int freq = 0;

	int ret = sscanf(buf, "%d\n", &freq);
	if(ret < 1)
		return count;

	if(freq == 0) {
		buzzer_device->enabled = 0;
		gpio_pwm_stop(buzzer_device->buzzer_pin);
	}
	else {                                             // set value; enable and start if disabled
		if(freq < 0)
			freq = -freq;

		buzzer_device->freq = freq;

		gpio_pwm_set_duty(buzzer_device->buzzer_pin, 250000);
		gpio_pwm_set_period(buzzer_device->buzzer_pin, 1000000000 / freq);
		if(!buzzer_device->enabled) {
			buzzer_device->enabled = 1;
			gpio_pwm_start(buzzer_device->buzzer_pin);
		}
	}
	return count;
}
